package org.example.demoschedules.service;

import lombok.extern.slf4j.Slf4j;
import org.example.demoschedules.model.OrderedMap;

import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 依序排班，就是把一组用户按照每日限定人数接龙式安排到每日班次中
 * <p>
 * 主要规则如下
 * 1、一个用户集合参数
 * 2、每日排班人数
 * 3、完全不排班日期集合，排班时候遇到集合中的日期跳过
 * 4、排班时，从用户集合中按顺序依次读取用户存到排班集合中，取到集合最后一个用户后再从集合第一个用户开始，如此循环
 * 5、前一周排班集合数据，用于对第4点排班时进行衔接排班使用。排班时读取上一日排班的最后一个用户，
 * 然后以该用户在集合中的位置为起点，往后一次读取用户排班。如果上一日最后一个用户在新的用户集合中不存在，则再往前找一个用户，直到找到一个存在用户集合的用户。
 * 如果找完前面的所有数据都没有找到用户（这种情况可以认为是完全新的一组用户来排班），则从用户集合的第一个用户开始排班。
 * </p>
 *
 * @author 单红宇
 * @since 2024/10/15 19:08
 */
@Slf4j
public class ScheduleBySequence {

    /**
     * 为指定日期排班，排班用户从前面几日排班的用户延续
     * 采用递归的方式排班
     *
     * @param startDate       指定排班的日期
     * @param members         所有用户集合
     * @param dailyUserCount  每日排班的用户数
     * @param beforeSchedules 前面的排班集合，没有数量限定
     * @param nonWorkingDates 完全不排班的日期集合
     * @param days            累计排班天数
     * @return 排班集合结果
     */
    public OrderedMap<LocalDate, OrderedMap<String, WorkStatus>> generateSchedule(
            LocalDate startDate, List<String> members, int dailyUserCount,
            OrderedMap<LocalDate, OrderedMap<String, WorkStatus>> beforeSchedules,
            Set<LocalDate> nonWorkingDates, int days) {
        OrderedMap<LocalDate, OrderedMap<String, WorkStatus>> schedules = this.deepCopy(beforeSchedules);
        this.recursionSchedule(startDate, members, dailyUserCount, schedules, nonWorkingDates, days);
        // 删除beforeSchedules
        beforeSchedules.keySet().forEach(schedules::remove);
        return schedules;
    }

    /**
     * 为指定日期排班，排班用户从前面几日排班的用户延续
     * 采用递归的方式排班
     *
     * @param scheduleDate    指定排班的日期
     * @param members         所有用户集合
     * @param dailyUserCount  每日排班的用户数
     * @param beforeSchedules 前面的排班集合，没有数量限定
     * @param nonWorkingDates 完全不排班的日期集合
     * @param days            累计排班天数
     */
    @SuppressWarnings("java:S3776")
    private void recursionSchedule(LocalDate scheduleDate, List<String> members, int dailyUserCount,
                                     OrderedMap<LocalDate, OrderedMap<String, WorkStatus>> beforeSchedules,
                                     Set<LocalDate> nonWorkingDates, int days) {
        if (days < 1) {
            return;
        }
        if (members.size() < dailyUserCount) {
            throw new IllegalArgumentException("排班的用户需要大于每日排班人数");
        }
        // 无需排班的日期，跳过
        if (!nonWorkingDates.contains(scheduleDate)) {
            // 读取上一个排班的最后一个用户（并且存在于本次排班用户集合中的用户）
            String lastMember = null;
            boolean loopFlag = true;
            for (int i = beforeSchedules.size() - 1; loopFlag && i >= 0; i--) {
                OrderedMap<String, WorkStatus> prevSchedule = beforeSchedules.getEntryAt(i).getValue();
                if (prevSchedule == null || prevSchedule.isEmpty()) {
                    continue;
                }
                for (int j = prevSchedule.size() - 1; loopFlag && j >= 0; j--) {
                    Map.Entry<String, WorkStatus> entry = prevSchedule.getEntryAt(j);
                    if (entry.getValue() == WorkStatus.WORK && members.contains(entry.getKey())) {
                        lastMember = entry.getKey();
                        loopFlag = false;
                    }
                }
            }
            int startMemberIndex = lastMember != null ? members.indexOf(lastMember) + 1 : 0;

            OrderedMap<String, WorkStatus> todaySchedule = new OrderedMap<>();
            for (int i = 0; i < dailyUserCount; i++, startMemberIndex++) {
                if (startMemberIndex == members.size()) {
                    startMemberIndex = 0;
                }
                todaySchedule.put(members.get(startMemberIndex), WorkStatus.WORK);
            }

            beforeSchedules.put(scheduleDate, todaySchedule);
        }
        // 剩余排班天数
        int remainingDays = days - 1;
        this.recursionSchedule(scheduleDate.plusDays(1L), members, dailyUserCount, beforeSchedules, nonWorkingDates, remainingDays);
    }

    /**
     * deepCopy
     *
     * @param original original
     * @return OrderedMap
     */
    private OrderedMap<LocalDate, OrderedMap<String, WorkStatus>> deepCopy(
            OrderedMap<LocalDate, OrderedMap<String, WorkStatus>> original) {
        OrderedMap<LocalDate, OrderedMap<String, WorkStatus>> copy = new OrderedMap<>();

        for (Map.Entry<LocalDate, OrderedMap<String, WorkStatus>> entry : original.entrySet()) {
            LocalDate key = entry.getKey();
            OrderedMap<String, WorkStatus> value = entry.getValue();

            // 深拷贝内部的 OrderedMap
            OrderedMap<String, WorkStatus> innerCopy = new OrderedMap<>();
            for (Map.Entry<String, WorkStatus> innerEntry : value.entrySet()) {
                String innerKey = innerEntry.getKey();
                WorkStatus innerValue = innerEntry.getValue();
                innerCopy.put(innerKey, innerValue);
            }
            copy.put(key, innerCopy);
        }
        return copy;
    }

    /**
     * 打印排班表
     *
     * @param schedules schedules
     */
    public void printSchedule(OrderedMap<LocalDate, OrderedMap<String, WorkStatus>> schedules) {
        schedules.entrySet().forEach(entry -> {
            log.info("日期: {}", entry.getKey());
            log.info("用户: {}", entry.getValue());
        });
    }

}
